package com.heima.showWhatTheIOC.factory;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Properties;

/**
 * @Author: Ethan Yankang
 * @Program: SpringBilibili
 * @Date: 2024-09-01 15:50
 **/

/*
 * @description 这是新增的静态工厂类用来生产具体使用的bean.将代码修改放置在了工厂类里面。
 * 但是依旧有问题，因为还是需要在return new XXXImpl;这里手动修改代码。
 * 呢么可不可以达到不需要手动修改代码就能够实现内容更新呢？
 * 答案是可以，使用的是反射+配置文件/注解！！
 *
 *外部配置文件方式：
 * 将Java对象写在外部文件里面，不需要再在动Java代码了，只是去读配置文件就可以了，这就是巨大的成功！
 *
 *
 *
 * @params
 * @return
 */

public class BeanFactory {

    //java提供了专门读取外部配置文件的类Properties
    private static final Properties properties;

    /*
     * @description 新增HashMap缓存作为确保单例的机制
     * @params
     * @return
     */

    private static HashMap<String, Object> cache = new HashMap<>();


    /*
     * @description 处理读取配置文件
     * @params
     * @return
     */

    static {
        properties = new Properties();
        try {
            properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("factory.properties"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static Object getDao(String beanName) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//         版本一，直接返回对象
//           return new HelloDaoImpl();

//        版本二，反射机制创建对象
        /*
         * @description value 在这里是对象的全类名
         * @params
         * @return
         */

//       版本三，Bean统统先加入缓存，判断是否在缓存中存在相同的Bean，存在就直接返回。确保单例。

//
            /*
             * @description 这里加重量级锁的原因是防止高并发出现安全问题
             * @params
             *         面试经典 —————————   为什么这里要进行两次if if (!cache.containsKey(beanName))判断？？？
             *
             * 1.第一层if————快速响应
             * 这里实际上是为了快速检查缓存中是否存在该实例，存在直接返回，实现快速响应
             *
             * 2.第二层if————多线程安全保障
             * 这里是为了防止在进行第一次if判断是，有多个线程同时进入了第一层if，这时，当前一个线程在里面创建好实例释放锁后，如果没有第二层if
             * 那么紧跟在后面的线程就会进入同步块，若两个线程携带的beanName相同，那么就还是会出现同一个beanName创建多个对象的情况！！！
             *
             *
             * 与延时双删很像(二次删除)！！
             * @return
             */

//            先判断缓存中是否存在相同key值的Bean(即相同类型)
            if (!cache.containsKey(beanName)) {
            synchronized (BeanFactory.class) {
                if (!cache.containsKey(beanName)) {
                    try {
                        String value = properties.getProperty(beanName);
                        Class clazz = Class.forName(value);
                        cache.put(beanName, clazz.getConstructor(null).newInstance(null));

                    } catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException |
                             SecurityException |
                             IllegalAccessException | InstantiationException | IllegalArgumentException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
        return cache.get(beanName);
    }
}
